#ifndef __HDVB_RECEIVER_H
#define __HDVB_RECEIVER_H
/*
    libhdvbd - Library for building sophisticated DVB applications
    Copyright (C) 2012 Tony Houghton <h@realh.co.uk>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/* A receiver represents a single frontend etc on an adapter */

#include <glib-object.h>

#include <linux/dvb/dmx.h>
#include <linux/dvb/frontend.h>

#include <gudev/gudev.h>

#include <hdvb/tuning.h>

G_BEGIN_DECLS

/* This should be the optimum size for reading data from dvr */
//#define HDVB_RECEIVER_DVR_BUFFER_SIZE (8192 - 8192 % 188)
#define HDVB_RECEIVER_DVR_BUFFER_SIZE 8192

/* Signals */

/**
 * HdvbReceiver::lock:
 * @rcvr: (transfer none):
 * @locked: Whether signal lock was achieved within timeout (%TRUE)
 *          or failed (%FALSE)
 */

/**
 * HdvbReceiver::detune:
 * @rcvr: (transfer none):
 * @tuning_str: New tuning string or "" if closing
 *
 * This signal is raised when receiver is detuned or just before retuning.
 */

/**
 * HdvbReceiver::removed:
 * @rcvr: (transfer none):
 *
 * Raised when a hotpluggable receiver is removed
 */

/**
 * HdvbReceiver::reserve:
 * @rcvr: (transfer none):
 *
 * Raised by calling hdvb_receiver_reserve(). This should be used eg to cancel
 * background EIT scanning.
 */

/**
 * HdvbReceiver::event-status:
 * @rcvr: (transfer none):
 * @running: Whether programme is starting (%TRUE) or stopping (%FALSE)
 *
 * Raised by calling hdvb_receiver_event_status(). Typically called by an EIT
 * filter to indicate when a programme is starting or stopping and timers can
 * start or stop recording accordingly.
 */


/* Properties */

/**
 * HdvbReceiver:adapter:
 * adapter number
 */

/**
 * HdvbReceiver:frontend:
 * frontend number
 */

/**
 * HdvbReceiver:udev:
 * #GUdevDevice used to create this receiver
 *
 * A reference is added because that's standard GObject behaviour
 * for properties.
 */

#define HDVB_RECEIVER_SIGNAL_LOCK 0
#define HDVB_RECEIVER_SIGNAL_DETUNE 1
#define HDVB_RECEIVER_SIGNAL_RESERVE 2
#define HDVB_RECEIVER_SIGNAL_REMOVED 3
#define HDVB_RECEIVER_SIGNAL_EVENT_STATUS 4

extern guint hdvb_receiver_signals[];

#define HDVB_TYPE_RECEIVER (hdvb_receiver_get_type())
#define HDVB_RECEIVER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \
        HDVB_TYPE_RECEIVER, HdvbReceiver))
#define HDVB_IS_RECEIVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \
        HDVB_TYPE_RECEIVER))
#define HDVB_RECEIVER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \
        HDVB_TYPE_RECEIVER, HdvbReceiverClass))
#define HDVB_IS_RECEIVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), \
        HDVB_TYPE_RECEIVER))
#define HDVB_RECEIVER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \
        HDVB_TYPE_RECEIVER, HdvbReceiverClass))

typedef struct _HdvbReceiver HdvbReceiver;
typedef struct _HdvbReceiverClass HdvbReceiverClass;

typedef struct _HdvbStreamSrc *HdvbStreamSrcPtr;

typedef HdvbStreamSrcPtr (*HdvbStreamSrcFactory)(HdvbReceiver *,
        gpointer handle);
typedef void (*HdvbStreamSrcDeleter)(HdvbStreamSrcPtr);

struct _HdvbReceiver {
    GObject parent_instance;
    GUdevDevice *udev;
    int adapter;
    int frontend;
    int fe_fd;
    int dvr_fd;
    char *fe_name;
    char *dmx_name;
    char *dvr_name;
    GList *ts_filters;
    GRecMutex mutex;
    GIOChannel *lock_ioc;
    guint lock_tag;
    guint timeout_tag;
    char *tuning_str;
    gboolean start_pending; /* If TRUE the EIT filter should raise
                               "event-status" when it finds the following
                               event_id running on the following service_id */
    gboolean end_pending;   /* If TRUE the EIT filter should raise
                               "event-status" when it finds *other* than the
                               following event_id running on the following
                               service_id */
    int event_id;
    int service_id;
    HdvbStreamSrcPtr stream_src;
    HdvbStreamSrcFactory stream_src_factory;
    HdvbStreamSrcDeleter stream_src_deleter;
    gpointer factory_handle;
};

struct _HdvbReceiverClass {
    GObjectClass parent_class;
    int (*open_frontend)(HdvbReceiver *rcv);
    void (*close_frontend_full)(HdvbReceiver *rcv, gboolean emit_detune);
    int (*open_dvr)(HdvbReceiver *rcv);
    void (*close_dvr)(HdvbReceiver *rcv);
    int (*tune)(HdvbReceiver *rcv,
            const HdvbTuningData *td, const gchar *tuning_str,
            guint timeout);
    int (*request_ts_filter)(HdvbReceiver *rcv,
            int pid, dmx_pes_type_t pes_type);
    void (*unref_ts_filter)(HdvbReceiver *rcv, int pid);
    void (*poll_for_lock)(HdvbReceiver *rcv, guint timeout);
    int (*set_section_filter)(HdvbReceiver *rcv,
            const struct dmx_sct_filter_params *params);
    void (*reserve)(HdvbReceiver *rcv);
    void (*event_status)(HdvbReceiver *rcv, gboolean running);
    void (*set_stream_src_factory)(HdvbReceiver *, HdvbStreamSrcFactory,
            HdvbStreamSrcDeleter, gpointer);
    HdvbStreamSrcPtr (*get_stream_src)(HdvbReceiver *rcv);
    gboolean (*read_tuning_details)(HdvbReceiver *rcv, HdvbTuningData *td);
};

GType hdvb_receiver_get_type(void);

/**
 * hdvb_receiver_new:
 * @udev (transfer full): gudev device info
 * Returns: A new #HdvbReceiver
 */
HdvbReceiver *
hdvb_receiver_new(GUdevDevice *udev);

/**
 * hdvb_receiver_open_frontend:
 * @rcvr: (transfer none):
 * Returns: file descriptor for frontend, which is an existing fd if already
 *          open; derived classes should do the same
 */
int
hdvb_receiver_open_frontend(HdvbReceiver *rcv);

/**
 * hdvb_receiver_close_frontend:
 * @rcvr: (transfer none):
 * @emit_detune: Whether to emit detune signal
 */
void
hdvb_receiver_close_frontend_full(HdvbReceiver *rcv, gboolean emit_detune);

/**
 * hdvb_receiver_close_frontend:
 * @rcvr: (transfer none):
 */
inline static void
hdvb_receiver_close_frontend(HdvbReceiver *rcv)
{
    hdvb_receiver_close_frontend_full(rcv, TRUE);
}

/**
 * hdvb_receiver_open_dvr:
 * @rcvr: (transfer none):
 * Returns: file descriptor for dvr, which is an existing fd if already
 *          open; derived classes should do the same
 */
int
hdvb_receiver_open_dvr(HdvbReceiver *rcv);

/**
 * hdvb_receiver_close_dvr:
 * @rcvr: (transfer none):
 */
void
hdvb_receiver_close_dvr(HdvbReceiver *rcv);

/**
 * hdvb_receiver_tune:
 * @rcvr: (transfer none):
 * @td: (transfer none): tuning data
 * @tuning_str: string associated with td
 * @timeout: if != 0, hdvb_receiver_poll_for_lock() is called
 * Returns: 0 for success, or errno.
 */
int
hdvb_receiver_tune(HdvbReceiver *rcv,
        const HdvbTuningData *td, const gchar *tuning_str,
        guint timeout);

/**
 * hdvb_receiver_poll_for_lock:
 * @rcvr: (transfer none):
 * @timeout: Timeout in milliseconds
 *
 * Causes HdvbReceiver::lock to be raised when lock is achieved or
 * failed within timeout. NB this function does not block.
 */
void
hdvb_receiver_poll_for_lock(HdvbReceiver *rcv, guint timeout);

/**
 * hdvb_receiver_request_ts_filter:
 * @rcvr: (transfer none):
 * @pid: pid of requested susbstream
 * @pes_type: See dmx_pes_type_t in /usr/include/linux/dvb/dmx.h
 * Returns: 0 for success, or errno.
 *
 * Requests a TS PID to be streamed to the dvr device. The same PID may be
 * requested repeatedly, a reference count is used to know when to close
 * the filter.
 */
int
hdvb_receiver_request_ts_filter(HdvbReceiver *rcv,
        int pid, dmx_pes_type_t pes_type);

/**
 * hdvb_receiver_unref_ts_filter:
 * @rcvr: (transfer none):
 * @pid: pid of requested susbstream
 *
 * One less client requires this pid.
 */
void
hdvb_receiver_unref_ts_filter(HdvbReceiver *rcv, int pid);

/**
 * hdvb_receiver_set_section_filter:
 * @rcvr: (transfer none):
 * @params: (transfer none): filter parameters
 * Returns: a file descriptor for reading sections or -errno
 *
 * This type of filter has no reference counting.
 */
int
hdvb_receiver_set_section_filter(HdvbReceiver *rcv,
        const struct dmx_sct_filter_params *params);

/**
 * hdvb_receiver_get_fe_type:
 * @rcvr: (transfer none):
 *
 * Returns: frontend type or -1 if frontend can't be opened.
 */
fe_type_t
hdvb_receiver_get_fe_type(HdvbReceiver *rcv);

/**
 * hdvb_receiver_reserve:
 * @rcvr: (transfer none):
 *
 * Raise HdvbReceiver::reserve signal.
 */
void
hdvb_receiver_reserve(HdvbReceiver *rcv);

/**
 * hdvb_receiver_event_status:
 * @rcvr: (transfer none):
 * @running: Whether programmes is starting (%TRUE) or stopping (%FALSE)
 *
 * Raise HdvbReceiver::event-status signal.
 */
void
hdvb_receiver_event_status(HdvbReceiver *rcv, gboolean running);

/**
 * hdvb_receiver_lock:
 * @rcvr: (transfer none):
 *
 * Should probably be avoided outside derived classes.
 */
void
hdvb_receiver_lock(HdvbReceiver *rcv);

/**
 * hdvb_receiver_unlock:
 * @rcvr: (transfer none):
 *
 * Should probably be avoided outside derived classes.
 */
void
hdvb_receiver_unlock(HdvbReceiver *rcv);

/**
 * hdvb_receiver_is_tuned_to:
 * @rcvr: (transfer none):
 * Returns: Whether tuning_str matches the one rcvr is tuned to
 */
gboolean
hdvb_receiver_is_tuned_to(HdvbReceiver *rcv, const gchar *tuning_str);

/**
 * hdvb_receiver_read_tuning_details:
 * @rcvr: (transfer none):
 * @td: (return): tuning details
 * Returns: FALSE if not tuned.
 *
 * Reads current tuning details via FE_GET_PROPERTY.
 */
gboolean
hdvb_receiver_read_tuning_details(HdvbReceiver *rcv, HdvbTuningData *td);

/**
 * hdvb_receiver_set_stream_src_factory:
 * @rcvr: (transfer none):
 * @factory:
 * @deleter:
 * @handle: (transfer none): handle to be passed to factory.
 *
 * Sets function to create the receiver's stream source singleton.
 * Deleter is called when dvr is closed or when factory is replaced.
 */
void
hdvb_receiver_set_stream_src_factory(HdvbReceiver *rcv,
        HdvbStreamSrcFactory factory, HdvbStreamSrcDeleter deleter,
        gpointer handle);

/**
 * hdvb_receiver_get_stream_src:
 * @rcvr: (transfer none):
 * Returns: (transfer none): The receiver's stream source singleton.
 */
HdvbStreamSrcPtr
hdvb_receiver_get_stream_src(HdvbReceiver *rcv);



/* A list of all the receivers created by the udev factory below. Each item is
 * an HdvbReceiver * (or derivation) */
extern GList *hdvb_receiver_receivers;

/**
 * hdvb_receiver_list_receivers:
 * Returns: (transfer none) (element-type HdvbReceiver): A list of all the
 *          receivers created by the udev factory.
 */
GList *
hdvb_receiver_list_receivers(void);

/**
 * HdvbReceiverFactory:
 * @udev: (transfer full): gudev device info; transfer is full courtesy of
 *        #HdvbReceiver constructor
 * @handle: (transfer none): Passed to constructor
 * Returns: (allow-none): A new #HdvbReceiver or derived class,
            or %NULL if factory doesn't want to handle the device
*/
typedef HdvbReceiver *(*HdvbReceiverFactory)(GUdevDevice *udev,
        gpointer handle);

/**
 * hdvb_receiver_udev_init:
 * @factory: (allow-none): function to create an #HdvbReceiver from udev info;
 *          %NULL creates default HdvbReceiver
 * @handle: (transfer none) (allow none): Passed to constructor factory
 *
 * Initialises a GUdevClient so that the factory is called for each DVB device
 * present on the system and also called when a hotpluggable DVB device is
 * added. When a device is unplugged the "removed" signal is raised before
 * its HdvbReceiver is destroyed.
 */
void
hdvb_receiver_udev_init(HdvbReceiverFactory factory, gpointer handle);

/**
 * hdvb_receiver_get_by_numbers:
 * @adapter:
 * @frontend:
 * Returns: (transfer none) (allow none): A receiver from
 *          hdvb_receiver_receivers corresponding to the numbered adapter
 *          and frontend. %NULL if no match (also generates a non-fatal warning)
 */
HdvbReceiver *
hdvb_receiver_get_by_numbers(int adapter, int frontend);

G_END_DECLS

#endif /* __HDVB_RECEIVER_H */
